home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / SOAP / WSDL.php < prev   
Encoding:
PHP Script  |  2006-04-07  |  83.7 KB  |  2,124 lines

  1. <?php
  2. /**
  3.  * This file contains the code for dealing with WSDL access and services.
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 2.02 of the PHP license,
  8.  * that is bundled with this package in the file LICENSE, and is available at
  9.  * through the world-wide-web at http://www.php.net/license/2_02.txt.  If you
  10.  * did not receive a copy of the PHP license and are unable to obtain it
  11.  * through the world-wide-web, please send a note to license@php.net so we can
  12.  * mail you a copy immediately.
  13.  *
  14.  * @category   Web Services
  15.  * @package    SOAP
  16.  * @author     Dietrich Ayala <dietrich@ganx4.com> Original Author
  17.  * @author     Shane Caraveo <Shane@Caraveo.com>   Port to PEAR and more
  18.  * @author     Chuck Hagenbuch <chuck@horde.org>   Maintenance
  19.  * @author     Jan Schneider <jan@horde.org>       Maintenance
  20.  * @copyright  2003-2005 The PHP Group
  21.  * @license    http://www.php.net/license/2_02.txt  PHP License 2.02
  22.  * @link       http://pear.php.net/package/SOAP
  23.  */
  24.  
  25. require_once 'SOAP/Base.php';
  26. require_once 'SOAP/Fault.php';
  27. require_once 'HTTP/Request.php';
  28.  
  29. define('WSDL_CACHE_MAX_AGE', 43200);
  30. define('WSDL_CACHE_USE',     0); // set to zero to turn off caching
  31.  
  32. /**
  33.  * This class parses WSDL files, and can be used by SOAP::Client to
  34.  * properly register soap values for services.
  35.  *
  36.  * Originally based on SOAPx4 by Dietrich Ayala
  37.  * http://dietrich.ganx4.com/soapx4
  38.  *
  39.  * TODO:
  40.  *   add wsdl caching
  41.  *   refactor namespace handling ($namespace/$ns)
  42.  *   implement IDL type syntax declaration so we can generate WSDL
  43.  *
  44.  * @access public
  45.  * @package SOAP
  46.  * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
  47.  * @author Dietrich Ayala <dietrich@ganx4.com> Original Author
  48.  */
  49. class SOAP_WSDL extends SOAP_Base
  50. {
  51.     var $tns = null;
  52.     var $definition = array();
  53.     var $namespaces = array();
  54.     var $ns = array();
  55.     var $xsd = SOAP_XML_SCHEMA_VERSION;
  56.     var $complexTypes = array();
  57.     var $elements = array();
  58.     var $messages = array();
  59.     var $portTypes = array();
  60.     var $bindings = array();
  61.     var $imports = array();
  62.     var $services = array();
  63.     var $service = '';
  64.     var $uri = '';
  65.     var $docs = false;
  66.  
  67.     /**
  68.      * Proxy parameters
  69.      *
  70.      * @var array
  71.      */
  72.     var $proxy = null;
  73.  
  74.     var $trace = 0;
  75.  
  76.     /**
  77.      * Use WSDL cache
  78.      *
  79.      * @var boolean
  80.      */
  81.     var $cacheUse = null;
  82.  
  83.     /**
  84.      * Cache max lifetime (in seconds)
  85.      *
  86.      * @var int
  87.      */
  88.     var $cacheMaxAge = null;
  89.  
  90.     /**
  91.      * Class to use for WSDL parsing. Can be overridden for special
  92.      * cases, subclasses, etc.
  93.      * @var string $wsdlParserClass
  94.      */
  95.     var $wsdlParserClass = 'SOAP_WSDL_Parser';
  96.  
  97.     /**
  98.      * SOAP_WSDL constructor.
  99.      *
  100.      * @param string  $wsdl_uri     URL to WSDL file.
  101.      * @param array   $proxy        Contains options for HTTP_Request class (see HTTP/Request.php).
  102.      * @param boolean $cacheUse     Use WSDL caching. Defaults to false.
  103.      * @param integer $cacheMaxAge  Cache max lifetime (in seconds).
  104.      * @param boolean $docs         Parse documentation in the WSDL? Defaults to false.
  105.      *
  106.      * @access public
  107.      */
  108.     function SOAP_WSDL($wsdl_uri    = false,
  109.                        $proxy       = array(),
  110.                        $cacheUse    = WSDL_CACHE_USE,
  111.                        $cacheMaxAge = WSDL_CACHE_MAX_AGE,
  112.                        $docs        = false)
  113.     {
  114.         parent::SOAP_Base('WSDL');
  115.         $this->uri         = $wsdl_uri;
  116.         $this->proxy       = $proxy;
  117.         $this->cacheUse    = $cacheUse;
  118.         $this->cacheMaxAge = $cacheMaxAge;
  119.         $this->docs        = $docs;
  120.  
  121.         if ($wsdl_uri) {
  122.             $this->parseURL($wsdl_uri);
  123.             reset($this->services);
  124.             $this->service = key($this->services);
  125.         }
  126.     }
  127.  
  128.     function set_service($service)
  129.     {
  130.         if (array_key_exists($service, $this->services)) {
  131.             $this->service = $service;
  132.         }
  133.     }
  134.  
  135.     /**
  136.      * @deprecated use parseURL instead
  137.      */
  138.     function parse($wsdl_uri, $proxy = array())
  139.     {
  140.         $this->parseURL($wsdl_uri, $proxy);
  141.     }
  142.  
  143.     /**
  144.      * Fill the WSDL array tree with data from a WSDL file
  145.      *
  146.      * @param  string
  147.      * @param  array  proxy related parameters
  148.      * @return void
  149.      */
  150.     function parseURL($wsdl_uri, $proxy = array())
  151.     {
  152.         $parser =& new $this->wsdlParserClass($wsdl_uri, $this, $this->docs);
  153.  
  154.         if ($parser->fault) {
  155.             $this->_raiseSoapFault($parser->fault);
  156.         }
  157.     }
  158.  
  159.     /**
  160.      * Fill the WSDL array tree with data from one or more PHP class objects
  161.      *
  162.      * @param  mixed  $wsdl_obj An object or array of objects to add to the internal WSDL tree
  163.      * @param  string  $service_name Name of the WSDL <service>
  164.      * @param  string  $service_desc Optional description of the WSDL <service>
  165.      * @return void
  166.      */
  167.     function parseObject(&$wsdl_obj, $targetNamespace, $service_name, $service_desc = '')
  168.     {
  169.         $parser =& new SOAP_WSDL_ObjectParser($wsdl_obj, $this, $targetNamespace, $service_name, $service_desc);
  170.  
  171.          if ($parser->fault) {
  172.              $this->_raiseSoapFault($parser->fault);
  173.          }
  174.     }
  175.  
  176.     function getEndpoint($portName)
  177.     {
  178.         return (isset($this->services[$this->service]['ports'][$portName]['address']['location']))
  179.                 ? $this->services[$this->service]['ports'][$portName]['address']['location']
  180.                 : $this->_raiseSoapFault("no endpoint for port for $portName", $this->uri);
  181.     }
  182.  
  183.     function _getPortName($operation, $service)
  184.     {
  185.         if (isset($this->services[$service]['ports'])) {
  186.             foreach ($this->services[$service]['ports'] as $port => $portAttrs) {
  187.                 $type = $this->services[$service]['ports'][$port]['type'];
  188.                 if ($type == 'soap' &&
  189.                     isset($this->bindings[$portAttrs['binding']]['operations'][$operation])) {
  190.                     return $port;
  191.                 }
  192.             }
  193.         }
  194.         return null;
  195.     }
  196.  
  197.     /**
  198.      * find the name of the first port that contains an operation of
  199.      * name $operation.  always returns a the soap portName.
  200.      */
  201.     function getPortName($operation, $service = null)
  202.     {
  203.         if (!$service) $service = $this->service;
  204.         if (isset($this->services[$service]['ports'])) {
  205.             $portName = $this->_getPortName($operation, $service);
  206.             if ($portName) return $portName;
  207.         }
  208.         // Try any service in the WSDL.
  209.         foreach ($this->services as $serviceName => $service) {
  210.             if (isset($this->services[$serviceName]['ports'])) {
  211.                 $portName = $this->_getPortName($operation, $serviceName);
  212.                 if ($portName) {
  213.                     $this->service = $serviceName;
  214.                     return $portName;
  215.                 }
  216.             }
  217.         }
  218.         return $this->_raiseSoapFault("no operation $operation in wsdl", $this->uri);
  219.     }
  220.  
  221.     function getOperationData($portName, $operation)
  222.     {
  223.         if (isset($this->services[$this->service]['ports'][$portName]['binding'])
  224.             && $binding = $this->services[$this->service]['ports'][$portName]['binding']) {
  225.             // get operation data from binding
  226.             if (is_array($this->bindings[$binding]['operations'][$operation])) {
  227.                 $opData = $this->bindings[$binding]['operations'][$operation];
  228.             }
  229.             // get operation data from porttype
  230.             $portType = $this->bindings[$binding]['type'];
  231.             if (!$portType) {
  232.                 return $this->_raiseSoapFault("no port type for binding $binding in wsdl " . $this->uri);
  233.             }
  234.             if (is_array($this->portTypes[$portType][$operation])) {
  235.                 if (isset($this->portTypes[$portType][$operation]['parameterOrder']))
  236.                     $opData['parameterOrder'] = $this->portTypes[$portType][$operation]['parameterOrder'];
  237.                 $opData['input'] = array_merge($opData['input'], $this->portTypes[$portType][$operation]['input']);
  238.                 $opData['output'] = array_merge($opData['output'], $this->portTypes[$portType][$operation]['output']);
  239.             }
  240.             if (!$opData)
  241.                 return $this->_raiseSoapFault("no operation $operation for port $portName, in wsdl", $this->uri);
  242.             $opData['parameters'] = false;
  243.             if (isset($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']))
  244.                 $opData['namespace'] = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace'];
  245.             // message data from messages
  246.             $inputMsg = $opData['input']['message'];
  247.             if (is_array($this->messages[$inputMsg])) {
  248.                 foreach ($this->messages[$inputMsg] as $pname => $pattrs) {
  249.                     if ($opData['style'] == 'document' && $opData['input']['use'] == 'literal'
  250.                         && $pname == 'parameters') {
  251.                         $opData['parameters'] = true;
  252.                         $opData['namespace'] = $this->namespaces[$pattrs['namespace']];
  253.                         $el = $this->elements[$pattrs['namespace']][$pattrs['type']];
  254.                         if (isset($el['elements'])) {
  255.                             foreach ($el['elements'] as $elname => $elattrs) {
  256.                                 $opData['input']['parts'][$elname] = $elattrs;
  257.                             }
  258.                         }
  259.                     } else {
  260.                         $opData['input']['parts'][$pname] = $pattrs;
  261.                     }
  262.                 }
  263.             }
  264.             $outputMsg = $opData['output']['message'];
  265.             if (is_array($this->messages[$outputMsg])) {
  266.                 foreach ($this->messages[$outputMsg] as $pname => $pattrs) {
  267.                     if ($opData['style'] == 'document' && $opData['output']['use'] == 'literal'
  268.                         && $pname == 'parameters') {
  269.  
  270.                         $el = $this->elements[$pattrs['namespace']][$pattrs['type']];
  271.                         if (isset($el['elements'])) {
  272.                             foreach ($el['elements'] as $elname => $elattrs) {
  273.                                 $opData['output']['parts'][$elname] = $elattrs;
  274.                             }
  275.                         }
  276.                     } else {
  277.                         $opData['output']['parts'][$pname] = $pattrs;
  278.                     }
  279.                 }
  280.             }
  281.             return $opData;
  282.         }
  283.         return $this->_raiseSoapFault("no binding for port $portName in wsdl", $this->uri);
  284.     }
  285.  
  286.     function matchMethod(&$operation)
  287.     {
  288.         // Overloading lowercases function names :(
  289.         foreach ($this->services[$this->service]['ports'] as $port => $portAttrs) {
  290.             foreach (array_keys($this->bindings[$portAttrs['binding']]['operations']) as $op) {
  291.                 if (strcasecmp($op, $operation) == 0) {
  292.                     $operation = $op;
  293.                 }
  294.             }
  295.         }
  296.     }
  297.  
  298.     /**
  299.      * getDataHandler
  300.      *
  301.      * Given a datatype, what function handles the processing?
  302.      * this is used for doc/literal requests where we receive
  303.      * a datatype, and we need to pass it to a method in out
  304.      * server class
  305.      *
  306.      * @param string datatype
  307.      * @param string namespace
  308.      * @returns string methodname
  309.      * @access public
  310.      */
  311.     function getDataHandler($datatype, $namespace)
  312.     {
  313.         // see if we have an element by this name
  314.         if (isset($this->namespaces[$namespace]))
  315.             $namespace = $this->namespaces[$namespace];
  316.         if (isset($this->ns[$namespace])) {
  317.             $nsp = $this->ns[$namespace];
  318.             //if (!isset($this->elements[$nsp]))
  319.             //    $nsp = $this->namespaces[$nsp];
  320.             if (isset($this->elements[$nsp][$datatype])) {
  321.                 $checkmessages = array();
  322.                 // find what messages use this datatype
  323.                 foreach ($this->messages as $messagename => $message) {
  324.                     foreach ($message as $partname => $part) {
  325.                         if ($part['type'] == $datatype) {
  326.                             $checkmessages[] = $messagename;
  327.                             break;
  328.                         }
  329.                     }
  330.                 }
  331.                 // find the operation that uses this message
  332.                 $dataHandler = null;
  333.                 foreach($this->portTypes as $portname => $porttype) {
  334.                     foreach ($porttype as $opname => $opinfo) {
  335.                         foreach ($checkmessages as $messagename) {
  336.                             if ($opinfo['input']['message'] == $messagename) {
  337.                                 return $opname;
  338.                             }
  339.                         }
  340.                     }
  341.                 }
  342.             }
  343.         }
  344.         return null;
  345.     }
  346.  
  347.     function getSoapAction($portName, $operation)
  348.     {
  349.         if (isset($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction']) &&
  350.             $soapAction = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction']) {
  351.             return $soapAction;
  352.         }
  353.         return false;
  354.     }
  355.  
  356.     function getNamespace($portName, $operation)
  357.     {
  358.         if (isset($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]) &&
  359.             isset($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']) &&
  360.             $namespace = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']) {
  361.             return $namespace;
  362.         }
  363.         return false;
  364.     }
  365.  
  366.     function getNamespaceAttributeName($namespace)
  367.     {
  368.         /* If it doesn't exist at first, flip the array and check
  369.          * again. */
  370.         if (empty($this->ns[$namespace])) {
  371.             $this->ns = array_flip($this->namespaces);
  372.         }
  373.         /* If it doesn't exist now, add it. */
  374.         if (empty($this->ns[$namespace])) {
  375.             return $this->addNamespace($namespace);
  376.         }
  377.  
  378.         return $this->ns[$namespace];
  379.     }
  380.  
  381.     function addNamespace($namespace)
  382.     {
  383.         if (!empty($this->ns[$namespace])) {
  384.             return $this->ns[$namespace];
  385.         }
  386.  
  387.         $n = count($this->ns);
  388.         $attr = 'ns' . $n;
  389.         $this->namespaces['ns' . $n] = $namespace;
  390.         $this->ns[$namespace] = $attr;
  391.         return $attr;
  392.     }
  393.  
  394.     function _validateString($string)
  395.     {
  396.         // XXX this should be done sooner or later
  397.         // return true;
  398.         return preg_match("/^[\w_:#\/]+$/", $string);
  399.     }
  400.  
  401.     function _addArg(&$args, &$argarray, $argname)
  402.     {
  403.         if ($args) {
  404.             $args .= ', ';
  405.         }
  406.         $args .= "\$" . $argname;
  407.         if (!$this->_validateString($argname)) {
  408.             return null;
  409.         }
  410.         if ($argarray) {
  411.             $argarray .= ', ';
  412.         }
  413.         $argarray .= "'$argname' => \$" . $argname;
  414.     }
  415.  
  416.     function _elementArg(&$args, &$argarray, &$_argtype, $_argname)
  417.     {
  418.         $comments = '';
  419.         $el = $this->elements[$_argtype['namespace']][$_argtype['type']];
  420.         $tns = isset($this->ns[$el['namespace']]) ? $this->ns[$el['namespace']] : $_argtype['namespace'];
  421.  
  422.         if (!empty($el['complex']) || (isset($el['type']) && isset($this->complexTypes[$tns][$el['type']]))) {
  423.             // The element is a complex type.
  424.             $comments .= "        // {$_argtype['type']} is a ComplexType, refer to the WSDL for more info.\n";
  425.             $attrname = "{$_argtype['type']}_attr";
  426.             if (isset($el['type']) && isset($this->complexTypes[$tns][$el['type']]['attribute'])) {
  427.                 $comments .= "        // {$_argtype['type']} may require attributes, refer to the WSDL for more info.\n";
  428.             }
  429.             $comments .= "        \${$attrname}['xmlns'] = '{$this->namespaces[$_argtype['namespace']]}';\n";
  430.             $comments .= "        \${$_argtype['type']} =& new SOAP_Value('{$_argtype['type']}', false, \${$_argtype['type']}, \$$attrname);\n";
  431.             $this->_addArg($args, $argarray, $_argtype['type']);
  432.             if (isset($el['type']) && isset($this->complexTypes[$tns][$el['type']]['attribute'])) {
  433.                 if ($args) {
  434.                     $args .= ', ';
  435.                 }
  436.                 $args .= '$' . $attrname;
  437.             }
  438.         } elseif (isset($el['elements'])) {
  439.             foreach ($el['elements'] as $ename => $element) {
  440.                 $comments .= "        \$$ename =& new SOAP_Value('{{$this->namespaces[$element['namespace']]}}$ename', '" .
  441.                     (isset($element['type']) ? $element['type'] : false) . "', \$$ename);\n";
  442.                 $this->_addArg($args, $argarray, $ename);
  443.             }
  444.         } else {
  445.             $comments .= "        \$$_argname =& new SOAP_Value('{{$this->namespaces[$tns]}}$_argname', '{$el['type']}', \$$_argname);\n";
  446.             $this->_addArg($args, $argarray, $_argname);
  447.         }
  448.  
  449.         return $comments;
  450.     }
  451.  
  452.     function _complexTypeArg(&$args, &$argarray, &$_argtype, $_argname)
  453.     {
  454.         $comments = '';
  455.         if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']])) {
  456.             $comments  = "        // $_argname is a ComplexType {$_argtype['type']},\n";
  457.             $comments .= "        // refer to wsdl for more info\n";
  458.             if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']]['attribute'])) {
  459.                 $comments .= "        // $_argname may require attributes, refer to wsdl for more info\n";
  460.             }
  461.             $wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $_argtype['type'];
  462.             $comments .= "        \$$_argname =& new SOAP_Value('$_argname', '$wrapname', \$$_argname);\n";
  463.         }
  464.  
  465.         $this->_addArg($args, $argarray, $_argname);
  466.         return $comments;
  467.     }
  468.  
  469.     /**
  470.      * Generates stub code from the WSDL that can be saved to a file
  471.      * or eval'd into existence.
  472.      */
  473.     function generateProxyCode($port = '', $classname='')
  474.     {
  475.         $multiport = count($this->services[$this->service]['ports']) > 1;
  476.         if (!$port) {
  477.             reset($this->services[$this->service]['ports']);
  478.             $port = current($this->services[$this->service]['ports']);
  479.         }
  480.         // XXX currently do not support HTTP ports
  481.         if ($port['type'] != 'soap') {
  482.             return null;
  483.         }
  484.  
  485.         // XXX currentPort is BAD
  486.         $clienturl = $port['address']['location'];
  487.         if (!$classname) {
  488.             if ($multiport || $port) {
  489.                 $classname = 'WebService_' . $this->service . '_' . $port['name'];
  490.             } else {
  491.                 $classname = 'WebService_' . $this->service;
  492.             }
  493.             $classname = preg_replace('/[ .\-\(\)]+/', '_', $classname);
  494.         }
  495.  
  496.         if (!$this->_validateString($classname)) {
  497.             return null;
  498.         }
  499.  
  500.         if (is_array($this->proxy) && count($this->proxy)) {
  501.             $class = "class $classname extends SOAP_Client\n{\n" .
  502.             "    function $classname(\$path = '$clienturl')\n    {\n" .
  503.             "        \$this->SOAP_Client(\$path, 0, 0,\n" .
  504.             '                           array(';
  505.  
  506.             foreach ($this->proxy as $key => $val) {
  507.                 if (is_array($val)) {
  508.                     $class .= "'$key' => array(";
  509.                     foreach ($val as $key2 => $val2) {
  510.                         $class .= "'$key2' => '$val2', ";
  511.                     }
  512.                     $class .= ')';
  513.                 } else {
  514.                     $class .= "'$key' => '$val', ";
  515.                 }
  516.             }
  517.             $class .= "));\n    }\n";
  518.             $class = str_replace(', ))', '))', $class);
  519.         } else {
  520.             $class = "class $classname extends SOAP_Client\n{\n" .
  521.             "    function $classname(\$path = '$clienturl')\n    {\n" .
  522.             "        \$this->SOAP_Client(\$path, 0);\n" .
  523.             "    }\n";
  524.         }
  525.  
  526.         // get the binding, from that get the port type
  527.         $primaryBinding = $port['binding']; //$this->services[$this->service]['ports'][$port['name']]["binding"];
  528.         $primaryBinding = preg_replace("/^(.*:)/", '', $primaryBinding);
  529.         $portType = $this->bindings[$primaryBinding]['type'];
  530.         $portType = preg_replace("/^(.*:)/", '', $portType);
  531.         $style = $this->bindings[$primaryBinding]['style'];
  532.  
  533.         // XXX currentPortType is BAD
  534.         foreach ($this->portTypes[$portType] as $opname => $operation) {
  535.             $soapaction = isset($this->bindings[$primaryBinding]['operations'][$opname]['soapAction']) ?
  536.                 $this->bindings[$primaryBinding]['operations'][$opname]['soapAction'] :
  537.                 null;
  538.             if (isset($this->bindings[$primaryBinding]['operations'][$opname]['style'])) {
  539.                 $opstyle = $this->bindings[$primaryBinding]['operations'][$opname]['style'];
  540.             } else {
  541.                 $opstyle = $style;
  542.             }
  543.             $use = $this->bindings[$primaryBinding]['operations'][$opname]['input']['use'];
  544.             if ($use == 'encoded') {
  545.                 $namespace = $this->bindings[$primaryBinding]['operations'][$opname]['input']['namespace'];
  546.             } else {
  547.                 $bindingType = $this->bindings[$primaryBinding]['type'];
  548.                 $ns = $this->portTypes[$bindingType][$opname]['input']['namespace'];
  549.                 $namespace = $this->namespaces[$ns];
  550.             }
  551.  
  552.             $args = '';
  553.             $argarray = '';
  554.             $comments = '';
  555.             $wrappers = '';
  556.             foreach ($operation['input'] as $argname => $argtype) {
  557.                 if ($argname == 'message') {
  558.                     foreach ($this->messages[$argtype] as $_argname => $_argtype) {
  559.                         if ($opstyle == 'document' && $use == 'literal' &&
  560.                             $_argtype['name'] == 'parameters') {
  561.                             // The type or element refered to is used
  562.                             // for parameters.
  563.                             $elattrs = null;
  564.                             $element = $_argtype['element'];
  565.                             $el = $this->elements[$_argtype['namespace']][$_argtype['type']];
  566.  
  567.                             if ($el['complex']) {
  568.                                 $namespace = $this->namespaces[$_argtype['namespace']];
  569.                                 // XXX need to wrap the parameters in
  570.                                 // a soap_value.
  571.                             }
  572.                             if (isset($el['elements'])) {
  573.                                 foreach ($el['elements'] as $elname => $elattrs) {
  574.                                     // Is the element a complex type?
  575.                                     if (isset($this->complexTypes[$elattrs['namespace']][$elname])) {
  576.                                         $comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname);
  577.                                     } else {
  578.                                         $this->_addArg($args, $argarray, $elname);
  579.                                     }
  580.                                 }
  581.                             }
  582.                             if ($el['complex'] && $argarray) {
  583.                                 $wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $el['name'];
  584.                                 $comments .= "        \${$el['name']} =& new SOAP_Value('$wrapname', false, \$v = array($argarray));\n";
  585.                                 $argarray = "'{$el['name']}' => \${$el['name']}";
  586.                             }
  587.                         } else {
  588.                             if (isset($_argtype['element'])) {
  589.                                 // Element argument.
  590.                                 $comments .= $this->_elementArg($args, $argarray, $_argtype, $_argtype['type']);
  591.                             } else {
  592.                                 // Complex type argument.
  593.                                 $comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname);
  594.                             }
  595.                         }
  596.                     }
  597.                 }
  598.             }
  599.  
  600.             // Validate entries.
  601.  
  602.             // Operation names are function names, so try to make sure
  603.             // it's legal. This could potentially cause collisions,
  604.             // but let's try to make everything callable and see how
  605.             // many problems that causes.
  606.             $opname_php = preg_replace('/[ .\-\(\)]+/', '_', $opname);
  607.             if (!$this->_validateString($opname_php)) {
  608.                 return null;
  609.             }
  610.  
  611.             if ($argarray) {
  612.                 $argarray = "array($argarray)";
  613.             } else {
  614.                 $argarray = 'null';
  615.             }
  616.  
  617.             $class .= "    function &$opname_php($args)\n    {\n$comments$wrappers" .
  618.                 "        return \$this->call('$opname',\n" .
  619.                 "                           \$v = $argarray,\n" .
  620.                 "                           array('namespace' => '$namespace',\n" .
  621.                 "                                 'soapaction' => '$soapaction',\n" .
  622.                 "                                 'style' => '$opstyle',\n" .
  623.                 "                                 'use' => '$use'" .
  624.                 ($this->trace?",\n                                 'trace' => 1" : '') . "));\n" .
  625.                 "    }\n";
  626.         }
  627.  
  628.         $class .= "}\n";
  629.         return $class;
  630.     }
  631.  
  632.     function generateAllProxies()
  633.     {
  634.         $proxycode = '';
  635.         foreach (array_keys($this->services[$this->service]['ports']) as $key) {
  636.             $port =& $this->services[$this->service]['ports'][$key];
  637.             $proxycode .= $this->generateProxyCode($port);
  638.         }
  639.         return $proxycode;
  640.     }
  641.  
  642.     function &getProxy($port = '', $name = '')
  643.     {
  644.         $multiport = count($this->services[$this->service]['ports']) > 1;
  645.  
  646.         if (!$port) {
  647.             reset($this->services[$this->service]['ports']);
  648.             $port = current($this->services[$this->service]['ports']);
  649.         }
  650.  
  651.         if ($multiport || $port) {
  652.             $classname = 'WebService_' . $this->service . '_' . $port['name'];
  653.         } else {
  654.             $classname = 'WebService_' . $this->service;
  655.         }
  656.  
  657.         if ($name) {
  658.             $classname = $name . '_' . $classname;
  659.         }
  660.  
  661.         $classname = preg_replace('/[ .\-\(\)]+/', '_', $classname);
  662.         if (!class_exists($classname)) {
  663.             $proxy = $this->generateProxyCode($port, $classname);
  664.             require_once 'SOAP/Client.php';
  665.             eval($proxy);
  666.         }
  667.  
  668.         return new $classname;
  669.     }
  670.  
  671.     function &_getComplexTypeForElement($name, $namespace)
  672.     {
  673.         $t = null;
  674.         if (isset($this->ns[$namespace]) &&
  675.             isset($this->elements[$this->ns[$namespace]][$name]['type'])) {
  676.  
  677.             $type = $this->elements[$this->ns[$namespace]][$name]['type'];
  678.             $ns = $this->elements[$this->ns[$namespace]][$name]['namespace'];
  679.  
  680.             if (isset($this->complexTypes[$ns][$type])) {
  681.                 $t = $this->complexTypes[$ns][$type];
  682.             }
  683.         }
  684.         return $t;
  685.     }
  686.  
  687.     function getComplexTypeNameForElement($name, $namespace)
  688.     {
  689.         $t = $this->_getComplexTypeForElement($name, $namespace);
  690.         if ($t) {
  691.             return $t['name'];
  692.         }
  693.         return null;
  694.     }
  695.  
  696.     function getComplexTypeChildType($ns, $name, $child_ns, $child_name)
  697.     {
  698.         // is the type an element?
  699.         $t = $this->_getComplexTypeForElement($name, $ns);
  700.         if ($t) {
  701.             // no, get it from complex types directly
  702.             if (isset($t['elements'][$child_name]['type']))
  703.                 return $t['elements'][$child_name]['type'];
  704.         }
  705.         return null;
  706.     }
  707.  
  708.     function getSchemaType($type, $name, $type_namespace)
  709.     {
  710.         // see if it's a complex type so we can deal properly with
  711.         // SOAPENC:arrayType.
  712.         if ($name && $type) {
  713.             // XXX TODO:
  714.             // look up the name in the wsdl and validate the type.
  715.             foreach ($this->complexTypes as $ns => $types) {
  716.                 if (array_key_exists($type, $types)) {
  717.                     if (array_key_exists('type', $types[$type])) {
  718.                         list($arraytype_ns, $arraytype, $array_depth) = isset($types[$type]['arrayType'])?
  719.                             $this->_getDeepestArrayType($types[$type]['namespace'], $types[$type]['arrayType'])
  720.                             : array($this->namespaces[$types[$type]['namespace']], null, 0);
  721.                         return array($types[$type]['type'], $arraytype, $arraytype_ns, $array_depth);
  722.                     }
  723.                     if (array_key_exists('arrayType', $types[$type])) {
  724.                         list($arraytype_ns, $arraytype, $array_depth) =
  725.                             $this->_getDeepestArrayType($types[$type]['namespace'], $types[$type]['arrayType']);
  726.                         return array('Array', $arraytype, $arraytype_ns, $array_depth);
  727.                     }
  728.                     if (array_key_exists('elements', $types[$type]) &&
  729.                         array_key_exists($name, $types[$type]['elements'])) {
  730.                         $type = $types[$type]['elements']['type'];
  731.                         return array($type, null, $this->namespaces[$types[$type]['namespace']], null);
  732.                     }
  733.                 }
  734.             }
  735.         }
  736.         if ($type && $type_namespace) {
  737.             $arrayType = null;
  738.             // XXX TODO:
  739.             // this code currently handles only one way of encoding array types in wsdl
  740.             // need to do a generalized function to figure out complex types
  741.             $p = $this->ns[$type_namespace];
  742.             if ($p &&
  743.                 array_key_exists($p, $this->complexTypes) &&
  744.                 array_key_exists($type, $this->complexTypes[$p])) {
  745.                 if ($arrayType = $this->complexTypes[$p][$type]['arrayType']) {
  746.                     $type = 'Array';
  747.                 } elseif ($this->complexTypes[$p][$type]['order']=='sequence' &&
  748.                           array_key_exists('elements', $this->complexTypes[$p][$type])) {
  749.                     reset($this->complexTypes[$p][$type]['elements']);
  750.                     // assume an array
  751.                     if (count($this->complexTypes[$p][$type]['elements']) == 1) {
  752.                         $arg = current($this->complexTypes[$p][$type]['elements']);
  753.                         $arrayType = $arg['type'];
  754.                         $type = 'Array';
  755.                     } else {
  756.                         foreach ($this->complexTypes[$p][$type]['elements'] as $element) {
  757.                             if ($element['name'] == $type) {
  758.                                 $arrayType = $element['type'];
  759.                                 $type = $element['type'];
  760.                             }
  761.                         }
  762.                     }
  763.                 } else {
  764.                     $type = 'Struct';
  765.                 }
  766.                 return array($type, $arrayType, $type_namespace, null);
  767.             }
  768.         }
  769.         return array(null, null, null, null);
  770.     }
  771.  
  772.     /**
  773.      * Recurse through the WSDL structure looking for the innermost
  774.      * array type of multi-dimensional arrays.
  775.      *
  776.      * Takes a namespace prefix and a type, which can be in the form
  777.      * 'type' or 'type[]', and returns the full namespace URI, the
  778.      * type of the most deeply nested array type found, and the number
  779.      * of levels of nesting.
  780.      *
  781.      * @access private
  782.      * @return mixed array or nothing
  783.      */
  784.     function _getDeepestArrayType($nsPrefix, $arrayType)
  785.     {
  786.         static $trail = array();
  787.  
  788.         $arrayType = ereg_replace('\[\]$', '', $arrayType);
  789.  
  790.         // Protect against circular references
  791.         // XXX We really need to remove trail from this altogether (it's very inefficient and
  792.         // in the wrong place!) and put circular reference checking in when the WSDL info
  793.         // is generated in the first place
  794.         if (array_search($nsPrefix . ':' . $arrayType, $trail)) {
  795.             return array(null, null, -count($trail));
  796.         }
  797.  
  798.         if (array_key_exists($nsPrefix, $this->complexTypes) &&
  799.             array_key_exists($arrayType, $this->complexTypes[$nsPrefix]) &&
  800.             array_key_exists('arrayType', $this->complexTypes[$nsPrefix][$arrayType])) {
  801.             $trail[] = $nsPrefix . ':' . $arrayType;
  802.             $result = $this->_getDeepestArrayType($this->complexTypes[$nsPrefix][$arrayType]['namespace'],
  803.                                                   $this->complexTypes[$nsPrefix][$arrayType]['arrayType']);
  804.             return array($result[0], $result[1], $result[2] + 1);
  805.         }
  806.         return array($this->namespaces[$nsPrefix], $arrayType, 0);
  807.     }
  808.  
  809. }
  810.  
  811. class SOAP_WSDL_Cache extends SOAP_Base
  812. {
  813.     // Cache settings
  814.  
  815.     /**
  816.      * Use WSDL cache
  817.      *
  818.      * @var boolean
  819.      */
  820.     var $_cacheUse = null;
  821.  
  822.     /**
  823.      * Cache max lifetime (in seconds)
  824.      *
  825.      * @var int
  826.      */
  827.     var $_cacheMaxAge = null;
  828.  
  829.     /**
  830.      * SOAP_WSDL_Cache constructor
  831.      *
  832.      * @param  boolean use caching
  833.      * @param  int     cache max lifetime (in seconds)
  834.      * @access public
  835.      */
  836.     function SOAP_WSDL_Cache($cacheUse = WSDL_CACHE_USE,
  837.                              $cacheMaxAge = WSDL_CACHE_MAX_AGE)
  838.     {
  839.         parent::SOAP_Base('WSDLCACHE');
  840.         $this->_cacheUse = $cacheUse;
  841.         $this->_cacheMaxAge = $cacheMaxAge;
  842.     }
  843.  
  844.     /**
  845.      * _cacheDir
  846.      * return the path to the cache, if it doesn't exist, make it
  847.      */
  848.     function _cacheDir()
  849.     {
  850.         $dir = getenv("WSDLCACHE");
  851.         if (!$dir) $dir = " ./wsdlcache";
  852.         @mkdir($dir, 0700);
  853.         return $dir;
  854.     }
  855.  
  856.     /**
  857.      * Retrieves a file from cache if it exists, otherwise retreive from net,
  858.      * add to cache, and return from cache.
  859.      *
  860.      * @param  string   URL to WSDL
  861.      * @param  array    proxy parameters
  862.      * @param  int      expected MD5 of WSDL URL
  863.      * @access public
  864.      * @return string  data
  865.      */
  866.     function get($wsdl_fname, $proxy_params = array(), $cache = 0)
  867.     {
  868.         $cachename = $md5_wsdl = $file_data = '';
  869.         if ($this->_cacheUse) {
  870.             // Try to retrieve WSDL from cache
  871.             $cachename = SOAP_WSDL_Cache::_cacheDir() . '/' . md5($wsdl_fname). ' .wsdl';
  872.             if (file_exists($cachename)) {
  873.                 $wf = fopen($cachename, 'rb');
  874.                 if ($wf) {
  875.                     // Reading cached file
  876.                     $file_data = fread($wf, filesize($cachename));
  877.                     $md5_wsdl = md5($file_data);
  878.                     fclose($wf);
  879.                 }
  880.                 if ($cache) {
  881.                     if ($cache != $md5_wsdl) {
  882.                         return $this->_raiseSoapFault('WSDL Checksum error!', $wsdl_fname);
  883.                     }
  884.                 } else {
  885.                     $fi = stat($cachename);
  886.                     $cache_mtime = $fi[8];
  887.                     //print cache_mtime, time()
  888.                     if ($cache_mtime + $this->_cacheMaxAge < time()) {
  889.                         // expired
  890.                         $md5_wsdl = ''; // refetch
  891.                     }
  892.                 }
  893.             }
  894.         }
  895.  
  896.         if (!$md5_wsdl) {
  897.             // Not cached or not using cache. Retrieve WSDL from URL
  898.  
  899.             // is it a local file?
  900.             // this section should be replace by curl at some point
  901.             if (!preg_match('/^(https?|file):\/\//', $wsdl_fname)) {
  902.                 if (!file_exists($wsdl_fname)) {
  903.                     return $this->_raiseSoapFault("Unable to read local WSDL $wsdl_fname", $wsdl_fname);
  904.                 }
  905.                 if (function_exists('file_get_contents')) {
  906.                     $file_data = file_get_contents($wsdl_fname);
  907.                 } else {
  908.                     $file_data = implode('',file($wsdl_fname));
  909.                 }
  910.             } else {
  911.                 $uri = explode('?', $wsdl_fname);
  912.                 $rq =& new HTTP_Request($uri[0], $proxy_params);
  913.                 // the user agent HTTP_Request uses fouls things up
  914.                 if (isset($uri[1])) {
  915.                     $rq->addRawQueryString($uri[1]);
  916.                 }
  917.  
  918.                 if (isset($proxy_params['proxy_host']) &&
  919.                     isset($proxy_params['proxy_port']) &&
  920.                     isset($proxy_params['proxy_user']) &&
  921.                     isset($proxy_params['proxy_pass'])) {
  922.                     $rq->setProxy($proxy_params['proxy_host'], $proxy_params['proxy_port'],
  923.                                   $proxy_params['proxy_user'], $proxy_params['proxy_pass']);
  924.                 } elseif (isset($proxy_params['proxy_host']) &&
  925.                           isset($proxy_params['proxy_port'])) {
  926.                     $rq->setProxy($proxy_params['proxy_host'], $proxy_params['proxy_port']);
  927.                 }
  928.  
  929.                 $result = $rq->sendRequest();
  930.                 if (PEAR::isError($result)) {
  931.                     return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname," . $rq->getResponseCode(), $wsdl_fname);
  932.                 }
  933.                 $file_data = $rq->getResponseBody();
  934.                 if (!$file_data) {
  935.                     return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname, no http body", $wsdl_fname);
  936.                 }
  937.             }
  938.  
  939.             $md5_wsdl = md5($file_data);
  940.  
  941.             if ($this->_cacheUse) {
  942.                 $fp = fopen($cachename, "wb");
  943.                 fwrite($fp, $file_data);
  944.                 fclose($fp);
  945.             }
  946.         }
  947.         if ($this->_cacheUse && $cache && $cache != $md5_wsdl) {
  948.             return $this->_raiseSoapFault("WSDL Checksum error!", $wsdl_fname);
  949.         }
  950.         return $file_data;
  951.     }
  952.  
  953. }
  954.  
  955. class SOAP_WSDL_Parser extends SOAP_Base
  956. {
  957.  
  958.     /**
  959.      * Define internal arrays of bindings, ports, operations,
  960.      * messages, etc.
  961.      */
  962.     var $currentMessage;
  963.     var $currentOperation;
  964.     var $currentPortType;
  965.     var $currentBinding;
  966.     var $currentPort;
  967.  
  968.     /**
  969.      * Parser vars.
  970.      */
  971.     var $cache;
  972.  
  973.     var $tns = null;
  974.     var $soapns = array('soap');
  975.     var $uri = '';
  976.     var $wsdl = null;
  977.  
  978.     var $status = '';
  979.     var $element_stack = array();
  980.     var $parentElement = '';
  981.  
  982.     var $schema = '';
  983.     var $schemaStatus = '';
  984.     var $schema_stack = array();
  985.     var $currentComplexType;
  986.     var $schema_element_stack = array();
  987.     var $currentElement;
  988.  
  989.     /**
  990.      * constructor
  991.      */
  992.     function SOAP_WSDL_Parser($uri, &$wsdl, $docs = false)
  993.     {
  994.         parent::SOAP_Base('WSDLPARSER');
  995.         $this->cache =& new SOAP_WSDL_Cache($wsdl->cacheUse, $wsdl->cacheMaxAge);
  996.         $this->uri = $uri;
  997.         $this->wsdl = &$wsdl;
  998.         $this->docs = $docs;
  999.         $this->parse($uri);
  1000.     }
  1001.  
  1002.     function parse($uri)
  1003.     {
  1004.         // Check whether content has been read.
  1005.         $fd = $this->cache->get($uri, $this->wsdl->proxy);
  1006.         if (PEAR::isError($fd)) {
  1007.             return $this->_raiseSoapFault($fd);
  1008.         }
  1009.  
  1010.         // Create an XML parser.
  1011.         $parser = xml_parser_create();
  1012.         xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  1013.         xml_set_object($parser, $this);
  1014.         xml_set_element_handler($parser, 'startElement', 'endElement');
  1015.         if ($this->docs) {
  1016.             xml_set_character_data_handler($parser, 'characterData');
  1017.         }
  1018.  
  1019.         if (!xml_parse($parser, $fd, true)) {
  1020.             $detail = sprintf('XML error on line %d: %s',
  1021.                               xml_get_current_line_number($parser),
  1022.                               xml_error_string(xml_get_error_code($parser)));
  1023.             return $this->_raiseSoapFault("Unable to parse WSDL file $uri\n$detail");
  1024.         }
  1025.         xml_parser_free($parser);
  1026.         return true;
  1027.     }
  1028.  
  1029.     /**
  1030.      * start-element handler
  1031.      */
  1032.     function startElement($parser, $name, $attrs)
  1033.     {
  1034.         // Get element prefix.
  1035.         $qname =& new QName($name);
  1036.         if ($qname->ns) {
  1037.             $ns = $qname->ns;
  1038.             if ($ns && ((!$this->tns && strcasecmp($qname->name, 'definitions') == 0) || $ns == $this->tns)) {
  1039.                 $name = $qname->name;
  1040.             }
  1041.         }
  1042.         $this->currentTag = $qname->name;
  1043.         $this->parentElement = '';
  1044.         $stack_size = count($this->element_stack);
  1045.         if ($stack_size) {
  1046.             $this->parentElement = $this->element_stack[$stack_size - 1];
  1047.         }
  1048.         $this->element_stack[] = $this->currentTag;
  1049.  
  1050.         // Find status, register data.
  1051.         switch ($this->status) {
  1052.         case 'types':
  1053.             // sect 2.2 wsdl:types
  1054.             // children: xsd:schema
  1055.             $parent_tag = '';
  1056.             $stack_size = count($this->schema_stack);
  1057.             if ($stack_size) {
  1058.                 $parent_tag = $this->schema_stack[$stack_size - 1];
  1059.             }
  1060.  
  1061.             switch ($qname->name) {
  1062.             case 'schema':
  1063.                 // No parent should be in the stack.
  1064.                 if (!$parent_tag || $parent_tag == 'types') {
  1065.                     if (array_key_exists('targetNamespace', $attrs)) {
  1066.                         $this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']);
  1067.                     } else {
  1068.                         $this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns);
  1069.                     }
  1070.                     $this->wsdl->complexTypes[$this->schema] = array();
  1071.                     $this->wsdl->elements[$this->schema] = array();
  1072.                 }
  1073.                 break;
  1074.  
  1075.             case 'complexType':
  1076.                 if ($parent_tag == 'schema') {
  1077.                     $this->currentComplexType = $attrs['name'];
  1078.                     if (!isset($attrs['namespace'])) {
  1079.                         $attrs['namespace'] = $this->schema;
  1080.                     }
  1081.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType] = $attrs;
  1082.                     if (array_key_exists('base', $attrs)) {
  1083.                         $qn =& new QName($attrs['base']);
  1084.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name;
  1085.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $qn->ns;
  1086.                     } else {
  1087.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
  1088.                     }
  1089.                     $this->schemaStatus = 'complexType';
  1090.                 } else {
  1091.                     $this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = true;
  1092.                 }
  1093.                 break;
  1094.  
  1095.             case 'element':
  1096.                 if (isset($attrs['type'])) {
  1097.                     $qn =& new QName($attrs['type']);
  1098.                     $attrs['type'] = $qn->name;
  1099.                     if ($qn->ns && array_key_exists($qn->ns, $this->wsdl->namespaces)) {
  1100.                         $attrs['namespace'] = $qn->ns;
  1101.                     }
  1102.                 }
  1103.  
  1104.                 $parentElement = '';
  1105.                 $stack_size = count($this->schema_element_stack);
  1106.                 if ($stack_size > 0) {
  1107.                     $parentElement = $this->schema_element_stack[$stack_size - 1];
  1108.                 }
  1109.  
  1110.                 if (isset($attrs['ref'])) {
  1111.                     $qn =& new QName($attrs['ref']);
  1112.                     $this->currentElement = $qn->name;
  1113.                 } else {
  1114.                     $this->currentElement = $attrs['name'];
  1115.                 }
  1116.                 $this->schema_element_stack[] = $this->currentElement;
  1117.                 if (!isset($attrs['namespace'])) {
  1118.                     $attrs['namespace'] = $this->schema;
  1119.                 }
  1120.  
  1121.                 if ($parent_tag == 'schema') {
  1122.                     $this->wsdl->elements[$this->schema][$this->currentElement] = $attrs;
  1123.                     $this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = false;
  1124.                     $this->schemaStatus = 'element';
  1125.                 } elseif ($this->currentComplexType) {
  1126.                     // we're inside a complexType
  1127.                     if ((isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order']) &&
  1128.                          $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] == 'sequence')
  1129.                         && $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array') {
  1130.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['arrayType'] = isset($attrs['type']) ? $attrs['type'] : null;
  1131.                     }
  1132.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement] = $attrs;
  1133.                 } else {
  1134.                     $this->wsdl->elements[$this->schema][$parentElement]['elements'][$this->currentElement] = $attrs;
  1135.                 }
  1136.                 break;
  1137.  
  1138.             case 'complexContent':
  1139.             case 'simpleContent':
  1140.                 break;
  1141.  
  1142.             case 'extension':
  1143.             case 'restriction':
  1144.                 if ($this->schemaStatus == 'complexType') {
  1145.                     if (!empty($attrs['base'])) {
  1146.                         $qn =& new QName($attrs['base']);
  1147.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name;
  1148.  
  1149.                         // Types that extend from other types aren't
  1150.                         // *of* those types. Reflect this by denoting
  1151.                         // which type they extend. I'm leaving the
  1152.                         // 'type' setting here since I'm not sure what
  1153.                         // removing it might break at the moment.
  1154.                         if ($qname->name == 'extension') {
  1155.                             $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['extends'] = $qn->name;
  1156.                         }
  1157.                     } else {
  1158.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
  1159.                     }
  1160.                 }
  1161.                 break;
  1162.  
  1163.             case 'sequence':
  1164.                 if ($this->schemaStatus == 'complexType') {
  1165.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
  1166.                     if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
  1167.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
  1168.                     }
  1169.                 }
  1170.                 break;
  1171.  
  1172.             case 'all':
  1173.                 $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
  1174.                 if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
  1175.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
  1176.                 }
  1177.                 break;
  1178.  
  1179.             case 'choice':
  1180.                 $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
  1181.                 if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
  1182.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
  1183.                 }
  1184.  
  1185.             case 'attribute':
  1186.                 if ($this->schemaStatus == 'complexType') {
  1187.                     if (isset($attrs['name'])) {
  1188.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['attribute'][$attrs['name']] = $attrs;
  1189.                     } else {
  1190.                         if (isset($attrs['ref'])) {
  1191.                             $q =& new QName($attrs['ref']);
  1192.                             foreach ($attrs as $k => $v) {
  1193.                                 if ($k != 'ref' && strstr($k, $q->name)) {
  1194.                                     $vq =& new QName($v);
  1195.                                     if ($q->name == 'arrayType') {
  1196.                                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name. $vq->arrayInfo;
  1197.                                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
  1198.                                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $vq->ns;
  1199.                                     } else {
  1200.                                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name;
  1201.                                     }
  1202.                                 }
  1203.                             }
  1204.                         }
  1205.                     }
  1206.                 }
  1207.                 break;
  1208.             }
  1209.  
  1210.             $this->schema_stack[] = $qname->name;
  1211.             break;
  1212.  
  1213.         case 'message':
  1214.             // sect 2.3 wsdl:message child wsdl:part
  1215.             switch ($qname->name) {
  1216.             case 'part':
  1217.                 $qn = null;
  1218.                 if (isset($attrs['type'])) {
  1219.                     $qn =& new QName($attrs['type']);
  1220.                 } elseif (isset($attrs['element'])) {
  1221.                     $qn =& new QName($attrs['element']);
  1222.                 }
  1223.                 if ($qn) {
  1224.                     $attrs['type'] = $qn->name;
  1225.                     $attrs['namespace'] = $qn->ns;
  1226.                 }
  1227.                 $this->wsdl->messages[$this->currentMessage][$attrs['name']] = $attrs;
  1228.                 // error in wsdl
  1229.  
  1230.             case 'documentation':
  1231.                 break;
  1232.  
  1233.             default:
  1234.                 break;
  1235.             }
  1236.             break;
  1237.  
  1238.         case 'portType':
  1239.             // sect 2.4
  1240.             switch ($qname->name) {
  1241.             case 'operation':
  1242.                 // attributes: name
  1243.                 // children: wsdl:input wsdl:output wsdl:fault
  1244.                 $this->currentOperation = $attrs['name'];
  1245.                 $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation] = $attrs;
  1246.                 break;
  1247.  
  1248.             case 'input':
  1249.             case 'output':
  1250.             case 'fault':
  1251.                 // wsdl:input wsdl:output wsdl:fault
  1252.                 // attributes: name message parameterOrder(optional)
  1253.                 if ($this->currentOperation) {
  1254.                     if (isset($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name])) {
  1255.                         $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = array_merge($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name], $attrs);
  1256.                     } else {
  1257.                         $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = $attrs;
  1258.                     }
  1259.                     if (array_key_exists('message', $attrs)) {
  1260.                         $qn =& new QName($attrs['message']);
  1261.                         $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['message'] = $qn->name;
  1262.                         $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['namespace'] = $qn->ns;
  1263.                     }
  1264.                 }
  1265.                 break;
  1266.  
  1267.             case 'documentation':
  1268.                 break;
  1269.  
  1270.             default:
  1271.                 break;
  1272.             }
  1273.             break;
  1274.  
  1275.         case 'binding':
  1276.             $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
  1277.             switch ($ns) {
  1278.             case SCHEMA_SOAP:
  1279.                 // this deals with wsdl section 3 soap binding
  1280.                 switch ($qname->name) {
  1281.                 case 'binding':
  1282.                     // sect 3.3
  1283.                     // soap:binding, attributes: transport(required), style(optional, default = document)
  1284.                     // if style is missing, it is assumed to be 'document'
  1285.                     if (!isset($attrs['style'])) {
  1286.                         $attrs['style'] = 'document';
  1287.                     }
  1288.                     $this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs);
  1289.                     break;
  1290.  
  1291.                 case 'operation':
  1292.                     // sect 3.4
  1293.                     // soap:operation, attributes: soapAction(required), style(optional, default = soap:binding:style)
  1294.                     if (!isset($attrs['style'])) {
  1295.                         $attrs['style'] = $this->wsdl->bindings[$this->currentBinding]['style'];
  1296.                     }
  1297.                     if (isset($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation])) {
  1298.                         $this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = array_merge($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation], $attrs);
  1299.                     } else {
  1300.                         $this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = $attrs;
  1301.                     }
  1302.                     break;
  1303.  
  1304.                 case 'body':
  1305.                     // sect 3.5
  1306.                     // soap:body attributes:
  1307.                     // part - optional.  listed parts must appear in body, missing means all parts appear in body
  1308.                     // use - required. encoded|literal
  1309.                     // encodingStyle - optional.  space seperated list of encodings (uri's)
  1310.                     $this->wsdl->bindings[$this->currentBinding]
  1311.                                     ['operations'][$this->currentOperation][$this->opStatus] = $attrs;
  1312.                     break;
  1313.  
  1314.                 case 'fault':
  1315.                     // sect 3.6
  1316.                     // soap:fault attributes: name use  encodingStyle namespace
  1317.                     $this->wsdl->bindings[$this->currentBinding]
  1318.                                     ['operations'][$this->currentOperation][$this->opStatus] = $attrs;
  1319.                     break;
  1320.  
  1321.                 case 'header':
  1322.                     // sect 3.7
  1323.                     // soap:header attributes: message part use encodingStyle namespace
  1324.                     $this->wsdl->bindings[$this->currentBinding]
  1325.                                     ['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
  1326.                     break;
  1327.  
  1328.                 case 'headerfault':
  1329.                     // sect 3.7
  1330.                     // soap:header attributes: message part use encodingStyle namespace
  1331.                     $header = count($this->wsdl->bindings[$this->currentBinding]
  1332.                                     ['operations'][$this->currentOperation][$this->opStatus]['headers'])-1;
  1333.                     $this->wsdl->bindings[$this->currentBinding]
  1334.                                     ['operations'][$this->currentOperation][$this->opStatus]['headers'][$header]['fault'] = $attrs;
  1335.                     break;
  1336.  
  1337.                 case 'documentation':
  1338.                     break;
  1339.  
  1340.                 default:
  1341.                     // error!  not a valid element inside binding
  1342.                     break;
  1343.                 }
  1344.                 break;
  1345.  
  1346.             case SCHEMA_WSDL:
  1347.                 // XXX verify correct namespace
  1348.                 // for now, default is the 'wsdl' namespace
  1349.                 // other possible namespaces include smtp, http, etc. for alternate bindings
  1350.                 switch ($qname->name) {
  1351.                 case 'operation':
  1352.                     // sect 2.5
  1353.                     // wsdl:operation attributes: name
  1354.                     $this->currentOperation = $attrs['name'];
  1355.                     break;
  1356.  
  1357.                 case 'output':
  1358.                 case 'input':
  1359.                 case 'fault':
  1360.                     // sect 2.5
  1361.                     // wsdl:input attributes: name
  1362.                     $this->opStatus = $qname->name;
  1363.                     break;
  1364.  
  1365.                 case 'documentation':
  1366.                     break;
  1367.  
  1368.                 default:
  1369.                     break;
  1370.                 }
  1371.                 break;
  1372.  
  1373.             case SCHEMA_WSDL_HTTP:
  1374.                 switch ($qname->name) {
  1375.                 case 'binding':
  1376.                     // sect 4.4
  1377.                     // http:binding attributes: verb
  1378.                     // parent: wsdl:binding
  1379.                     $this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs);
  1380.                     break;
  1381.  
  1382.                 case 'operation':
  1383.                     // sect 4.5
  1384.                     // http:operation attributes: location
  1385.                     // parent: wsdl:operation
  1386.                     $this->wsdl->bindings[$this->currentBinding]['operations']
  1387.                                                         [$this->currentOperation] = $attrs;
  1388.                     break;
  1389.  
  1390.                 case 'urlEncoded':
  1391.                     // sect 4.6
  1392.                     // http:urlEncoded attributes: location
  1393.                     // parent: wsdl:input wsdl:output etc.
  1394.                     $this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus]
  1395.                                                         [$this->currentOperation]['uri'] = 'urlEncoded';
  1396.                     break;
  1397.  
  1398.                 case 'urlReplacement':
  1399.                     // sect 4.7
  1400.                     // http:urlReplacement attributes: location
  1401.                     // parent: wsdl:input wsdl:output etc.
  1402.                     $this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus]
  1403.                                                         [$this->currentOperation]['uri'] = 'urlReplacement';
  1404.                     break;
  1405.  
  1406.                 case 'documentation':
  1407.                     break;
  1408.  
  1409.                 default:
  1410.                     // error
  1411.                     break;
  1412.                 }
  1413.  
  1414.             case SCHEMA_MIME:
  1415.                 // sect 5
  1416.                 // all mime parts are children of wsdl:input, wsdl:output, etc.
  1417.                 // unsuported as of yet
  1418.                 switch ($qname->name) {
  1419.                 case 'content':
  1420.                     // sect 5.3 mime:content
  1421.                     // <mime:content part="nmtoken"? type="string"?/>
  1422.                     // part attribute only required if content is child of multipart related,
  1423.                     //        it contains the name of the part
  1424.                     // type attribute contains the mime type
  1425.                 case 'multipartRelated':
  1426.                     // sect 5.4 mime:multipartRelated
  1427.                 case 'part':
  1428.                 case 'mimeXml':
  1429.                     // sect 5.6 mime:mimeXml
  1430.                     // <mime:mimeXml part="nmtoken"?/>
  1431.                     //
  1432.                 case 'documentation':
  1433.                     break;
  1434.  
  1435.                 default:
  1436.                     // error
  1437.                     break;
  1438.                 }
  1439.  
  1440.             case SCHEMA_DIME:
  1441.                 // DIME is defined in:
  1442.                 // http://gotdotnet.com/team/xml_wsspecs/dime/WSDL-Extension-for-DIME.htm
  1443.                 // all DIME parts are children of wsdl:input, wsdl:output, etc.
  1444.                 // unsuported as of yet
  1445.                 switch ($qname->name) {
  1446.                 case 'message':
  1447.                     // sect 4.1 dime:message
  1448.                     // appears in binding section
  1449.                     $this->wsdl->bindings[$this->currentBinding]['dime'] = $attrs;
  1450.                     break;
  1451.  
  1452.                 default:
  1453.                     break;
  1454.                 }
  1455.  
  1456.             default:
  1457.                 break;
  1458.             }
  1459.             break;
  1460.  
  1461.         case 'service':
  1462.             $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
  1463.  
  1464.             switch ($qname->name) {
  1465.             case 'port':
  1466.                 // sect 2.6 wsdl:port attributes: name binding
  1467.                 $this->currentPort = $attrs['name'];
  1468.                 $this->wsdl->services[$this->currentService]['ports'][$this->currentPort] = $attrs;
  1469.                 // XXX hack to deal with binding namespaces
  1470.                 $qn =& new QName($attrs['binding']);
  1471.                 $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['binding'] = $qn->name;
  1472.                 $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['namespace'] = $qn->ns;
  1473.                 break;
  1474.  
  1475.             case 'address':
  1476.                 $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['address'] = $attrs;
  1477.                 // what TYPE of port is it?  SOAP or HTTP?
  1478.                 $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
  1479.                 switch ($ns) {
  1480.                 case SCHEMA_WSDL_HTTP:
  1481.                     $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='http';
  1482.                     break;
  1483.  
  1484.                 case SCHEMA_SOAP:
  1485.                     $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap';
  1486.                     break;
  1487.  
  1488.                 default:
  1489.                     // Shouldn't happen, we'll assume SOAP.
  1490.                     $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap';
  1491.                 }
  1492.  
  1493.                 break;
  1494.  
  1495.             case 'documentation':
  1496.                 break;
  1497.  
  1498.             default:
  1499.                 break;
  1500.             }
  1501.         }
  1502.  
  1503.         // Top level elements found under wsdl:definitions.
  1504.         switch ($qname->name) {
  1505.         case 'import':
  1506.             // sect 2.1.1 wsdl:import attributes: namespace location
  1507.             if ((isset($attrs['location']) || isset($attrs['schemaLocation'])) &&
  1508.                 !isset($this->wsdl->imports[$attrs['namespace']])) {
  1509.                 $uri = isset($attrs['location']) ? $attrs['location'] : $attrs['schemaLocation'];
  1510.                 $location = @parse_url($uri);
  1511.                 if (!isset($location['scheme'])) {
  1512.                     $base = @parse_url($this->uri);
  1513.                     $uri = $this->mergeUrl($base, $uri);
  1514.                 }
  1515.  
  1516.                 $this->wsdl->imports[$attrs['namespace']] = $attrs;
  1517.                 $import_parser_class = get_class($this);
  1518.                 $import_parser =& new $import_parser_class($uri, $this->wsdl, $this->docs);
  1519.                 if ($import_parser->fault) {
  1520.                     unset($this->wsdl->imports[$attrs['namespace']]);
  1521.                     return false;
  1522.                 }
  1523.                 $this->currentImport = $attrs['namespace'];
  1524.             }
  1525.             // Continue on to the 'types' case - lack of break; is
  1526.             // intentional.
  1527.  
  1528.         case 'types':
  1529.             // sect 2.2 wsdl:types
  1530.             $this->status = 'types';
  1531.             break;
  1532.  
  1533.         case 'schema':
  1534.             // We can hit this at the top level if we've been asked to
  1535.             // import an XSD file.
  1536.             if (!empty($attrs['targetNamespace'])) {
  1537.                 $this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']);
  1538.             } else {
  1539.                 $this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns);
  1540.             }
  1541.             $this->wsdl->complexTypes[$this->schema] = array();
  1542.             $this->wsdl->elements[$this->schema] = array();
  1543.             $this->schema_stack[] = $qname->name;
  1544.             $this->status = 'types';
  1545.             break;
  1546.  
  1547.         case 'message':
  1548.             // sect 2.3 wsdl:message attributes: name children:wsdl:part
  1549.             $this->status = 'message';
  1550.             if (isset($attrs['name'])) {
  1551.                 $this->currentMessage = $attrs['name'];
  1552.                 $this->wsdl->messages[$this->currentMessage] = array();
  1553.             }
  1554.             break;
  1555.  
  1556.         case 'portType':
  1557.             // sect 2.4 wsdl:portType
  1558.             // attributes: name
  1559.             // children: wsdl:operation
  1560.             $this->status = 'portType';
  1561.             $this->currentPortType = $attrs['name'];
  1562.             $this->wsdl->portTypes[$this->currentPortType] = array();
  1563.             break;
  1564.  
  1565.         case 'binding':
  1566.             // sect 2.5 wsdl:binding attributes: name type
  1567.             // children: wsdl:operation soap:binding http:binding
  1568.             if ($qname->ns && $qname->ns != $this->tns) {
  1569.                 break;
  1570.             }
  1571.             $this->status = 'binding';
  1572.             $this->currentBinding = $attrs['name'];
  1573.             $qn =& new QName($attrs['type']);
  1574.             $this->wsdl->bindings[$this->currentBinding]['type'] = $qn->name;
  1575.             $this->wsdl->bindings[$this->currentBinding]['namespace'] = $qn->ns;
  1576.             break;
  1577.  
  1578.         case 'service':
  1579.             // sect 2.7 wsdl:service attributes: name children: ports
  1580.             $this->currentService = $attrs['name'];
  1581.             $this->wsdl->services[$this->currentService]['ports'] = array();
  1582.             $this->status = 'service';
  1583.             break;
  1584.  
  1585.         case 'definitions':
  1586.             // sec 2.1 wsdl:definitions
  1587.             // attributes: name targetNamespace xmlns:*
  1588.             // children: wsdl:import wsdl:types wsdl:message wsdl:portType wsdl:binding wsdl:service
  1589.             $this->wsdl->definition = $attrs;
  1590.             foreach ($attrs as $key => $value) {
  1591.                 if (strstr($key, 'xmlns:') !== false) {
  1592.                     $qn =& new QName($key);
  1593.                     // XXX need to refactor ns handling.
  1594.                     $this->wsdl->namespaces[$qn->name] = $value;
  1595.                     $this->wsdl->ns[$value] = $qn->name;
  1596.                     if ($key == 'targetNamespace' ||
  1597.                         strcasecmp($value,SOAP_SCHEMA) == 0) {
  1598.                         $this->soapns[] = $qn->name;
  1599.                     } else {
  1600.                         if (in_array($value, $this->_XMLSchema)) {
  1601.                             $this->wsdl->xsd = $value;
  1602.                         }
  1603.                     }
  1604.                 }
  1605.             }
  1606.             if (isset($ns) && $ns) {
  1607.                 $namespace = 'xmlns:' . $ns;
  1608.                 if (!$this->wsdl->definition[$namespace]) {
  1609.                     return $this->_raiseSoapFault("parse error, no namespace for $namespace", $this->uri);
  1610.                 }
  1611.                 $this->tns = $ns;
  1612.             }
  1613.             break;
  1614.         }
  1615.     }
  1616.  
  1617.     /**
  1618.      * end-element handler.
  1619.      */
  1620.     function endElement($parser, $name)
  1621.     {
  1622.         $stacksize = count($this->element_stack);
  1623.         if ($stacksize) {
  1624.             if ($this->element_stack[$stacksize - 1] == 'definitions') {
  1625.                 $this->status = '';
  1626.             }
  1627.             array_pop($this->element_stack);
  1628.         }
  1629.  
  1630.         if (stristr($name, 'schema')) {
  1631.             array_pop($this->schema_stack);
  1632.             $this->schema = '';
  1633.         }
  1634.  
  1635.         if ($this->schema) {
  1636.             array_pop($this->schema_stack);
  1637.             if (count($this->schema_stack) <= 1) {
  1638.                 /* Correct the type for sequences with multiple
  1639.                  * elements. */
  1640.                 if (isset($this->currentComplexType) && isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])
  1641.                     && $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array'
  1642.                     && array_key_exists('elements', $this->wsdl->complexTypes[$this->schema][$this->currentComplexType])
  1643.                     && count($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements']) > 1) {
  1644.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
  1645.                 }
  1646.             }
  1647.             if (stristr($name, 'complexType')) {
  1648.                 $this->currentComplexType = '';
  1649.                 if (count($this->schema_element_stack)) {
  1650.                     $this->currentElement = array_pop($this->schema_element_stack);
  1651.                 } else {
  1652.                     $this->currentElement = '';
  1653.                 }
  1654.             } elseif (stristr($name, 'element')) {
  1655.                 if (count($this->schema_element_stack)) {
  1656.                     $this->currentElement = array_pop($this->schema_element_stack);
  1657.                 } else {
  1658.                     $this->currentElement = '';
  1659.                 }
  1660.             }
  1661.         }
  1662.     }
  1663.  
  1664.     /**
  1665.      * Element content handler.
  1666.      */
  1667.     function characterData($parser, $data)
  1668.     {
  1669.         // Store the documentation in the WSDL file.
  1670.         if ($this->currentTag == 'documentation') {
  1671.             $data = trim(preg_replace('/\s+/', ' ', $data));
  1672.             if (!strlen($data)) {
  1673.                 return;
  1674.             }
  1675.  
  1676.             switch ($this->status) {
  1677.             case 'service':
  1678.                 $ptr =& $this->wsdl->services[$this->currentService];
  1679.                 break;
  1680.  
  1681.             case 'portType':
  1682.                 $ptr =& $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation];
  1683.                 break;
  1684.  
  1685.             case 'binding':
  1686.                 $ptr =& $this->wsdl->bindings[$this->currentBinding];
  1687.                 break;
  1688.  
  1689.             case 'message':
  1690.                 $ptr =& $this->wsdl->messages[$this->currentMessage];
  1691.                 break;
  1692.  
  1693.             case 'operation':
  1694.                 break;
  1695.  
  1696.             case 'types':
  1697.                 if (isset($this->currentComplexType) &&
  1698.                     isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType])) {
  1699.                     if ($this->currentElement) {
  1700.                         $ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement];
  1701.                     } else {
  1702.                         $ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType];
  1703.                     }
  1704.                 }
  1705.                 break;
  1706.             }
  1707.  
  1708.             if (isset($ptr)) {
  1709.                 if (!isset($ptr['documentation'])) {
  1710.                     $ptr['documentation'] = '';
  1711.                 } else {
  1712.                     $ptr['documentation'] .= ' ';
  1713.                 }
  1714.                 $ptr['documentation'] .= $data;
  1715.             }
  1716.         }
  1717.     }
  1718.  
  1719.     /**
  1720.      * $parsed is an array returned by parse_url().
  1721.      *
  1722.      * @access private
  1723.      */
  1724.     function mergeUrl($parsed, $path)
  1725.     {
  1726.         if (!is_array($parsed)) {
  1727.             return false;
  1728.         }
  1729.  
  1730.         $uri = '';
  1731.         if (!empty($parsed['scheme'])) {
  1732.             $sep = (strtolower($parsed['scheme']) == 'mailto' ? ':' : '://');
  1733.             $uri = $parsed['scheme'] . $sep;
  1734.         }
  1735.  
  1736.         if (isset($parsed['pass'])) {
  1737.             $uri .= "$parsed[user]:$parsed[pass]@";
  1738.         } elseif (isset($parsed['user'])) {
  1739.             $uri .= "$parsed[user]@";
  1740.         }
  1741.  
  1742.         if (isset($parsed['host'])) {
  1743.             $uri .= $parsed['host'];
  1744.         }
  1745.         if (isset($parsed['port'])) {
  1746.             $uri .= ":$parsed[port]";
  1747.         }
  1748.         if ($path[0] != '/' && isset($parsed['path'])) {
  1749.             if ($parsed['path'][strlen($parsed['path']) - 1] != '/') {
  1750.                 $path = dirname($parsed['path']) . '/' . $path;
  1751.             } else {
  1752.                 $path = $parsed['path'] . $path;
  1753.             }
  1754.             $path = $this->_normalize($path);
  1755.         }
  1756.         $sep = $path[0] == '/' ? '' : '/';
  1757.         $uri .= $sep . $path;
  1758.  
  1759.         return $uri;
  1760.     }
  1761.  
  1762.     function _normalize($path_str)
  1763.     {
  1764.         $pwd = '';
  1765.         $strArr = preg_split('/(\/)/', $path_str, -1, PREG_SPLIT_NO_EMPTY);
  1766.         $pwdArr = '';
  1767.         $j = 0;
  1768.         for ($i = 0; $i < count($strArr); $i++) {
  1769.             if ($strArr[$i] != ' ..') {
  1770.                 if ($strArr[$i] != ' .') {
  1771.                     $pwdArr[$j] = $strArr[$i];
  1772.                     $j++;
  1773.                 }
  1774.             } else {
  1775.                 array_pop($pwdArr);
  1776.                 $j--;
  1777.             }
  1778.         }
  1779.         $pStr = implode('/', $pwdArr);
  1780.         $pwd = (strlen($pStr) > 0) ? ('/' . $pStr) : '/';
  1781.         return $pwd;
  1782.     }
  1783.  
  1784. }
  1785.  
  1786. /**
  1787.  * Parses the types and methods used in web service objects into the internal
  1788.  * data structures used by SOAP_WSDL.
  1789.  *
  1790.  * Assumes the SOAP_WSDL class is unpopulated to start with.
  1791.  *
  1792.  * @author Chris Coe <info@intelligentstreaming.com>
  1793.  */
  1794. class SOAP_WSDL_ObjectParser extends SOAP_Base
  1795. {
  1796.     /**
  1797.      * Target namespace for the WSDL document will have the following
  1798.      * prefix.
  1799.      */
  1800.     var $tnsPrefix = 'tns';
  1801.  
  1802.     /**
  1803.      * Reference to the SOAP_WSDL object to populate.
  1804.      */
  1805.     var $wsdl = null;
  1806.  
  1807.     /** Constructor
  1808.      *
  1809.      * @param  $objects Reference to the object or array of objects to parse
  1810.      * @param  $wsdl Reference to the SOAP_WSDL object to populate
  1811.      * @param  $targetNamespace The target namespace of schema types etc.
  1812.      * @param  $service_name Name of the WSDL <service>
  1813.      * @param  $service_desc Optional description of the WSDL <service>
  1814.      */
  1815.     function SOAP_WSDL_ObjectParser(&$objects, &$wsdl, $targetNamespace, $service_name, $service_desc = '')
  1816.     {
  1817.         parent::SOAP_Base('WSDLOBJECTPARSER');
  1818.  
  1819.         $this->wsdl = &$wsdl;
  1820.  
  1821.         // Set up the SOAP_WSDL object
  1822.         $this->_initialise($service_name);
  1823.  
  1824.         // Parse each web service object
  1825.         $wsdl_ref = (is_array($objects)? $objects : array(&$objects));
  1826.  
  1827.         foreach ($wsdl_ref as $ref_item) {
  1828.             if (!is_object($ref_item))
  1829.                 return $this->_raiseSoapFault('Invalid web service object passed to object parser', 'urn:' . get_class($object));
  1830.  
  1831.             if ($this->_parse($ref_item, $targetNamespace, $service_name) != true)
  1832.                 break;
  1833.         }
  1834.  
  1835.         // Build bindings from abstract data.
  1836.         if ($this->fault == null) {
  1837.             $this->_generateBindingsAndServices($targetNamespace, $service_name, $service_desc);
  1838.         }
  1839.     }
  1840.  
  1841.     /**
  1842.      * Initialise the SOAP_WSDL tree (destructive).
  1843.      *
  1844.      * If the object has already been initialised, the only effect
  1845.      * will be to change the tns namespace to the new service name.
  1846.      *
  1847.      * @param  $service_name Name of the WSDL <service>
  1848.      * @access private
  1849.      */
  1850.     function _initialise($service_name)
  1851.     {
  1852.         // Set up the basic namespaces that all WSDL definitions use.
  1853.         $this->wsdl->namespaces['wsdl'] = SCHEMA_WSDL;                                      // WSDL language
  1854.         $this->wsdl->namespaces['soap'] = SCHEMA_SOAP;                                      // WSDL SOAP bindings
  1855.         $this->wsdl->namespaces[$this->tnsPrefix] = 'urn:' . $service_name;                 // Target namespace
  1856.         $this->wsdl->namespaces['xsd'] = array_search('xsd', $this->_namespaces);           // XML Schema
  1857.         $this->wsdl->namespaces['SOAP-ENC'] = array_search('SOAP-ENC', $this->_namespaces); // SOAP types
  1858.  
  1859.         // XXX Refactor $namespace/$ns for Shane :-)
  1860.         unset($this->wsdl->ns['urn:' . $service_name]);
  1861.         $this->wsdl->ns += array_flip($this->wsdl->namespaces);
  1862.  
  1863.         // Imports are not implemented in WSDL generation from classes.
  1864.         // *** <wsdl:import> ***
  1865.     }
  1866.  
  1867.     /**
  1868.      * Parser - takes a single object to add to tree
  1869.      * (non-destructive).
  1870.      *
  1871.      * @param  $object Reference to the object to parse
  1872.      * @param  $service_name Name of the WSDL <service>
  1873.     * @access private
  1874.      */
  1875.     function _parse(&$object, $schemaNamespace, $service_name)
  1876.     {
  1877.         // Create namespace prefix for the schema
  1878.         // XXX not very elegant :-(
  1879.  
  1880.         list($schPrefix, $foo) = $this->_getTypeNs('{' . $schemaNamespace.'}');
  1881.         unset($foo);
  1882.  
  1883.         // Parse all the types defined by the object in whatever
  1884.         // schema language we are using (currently __typedef arrays)
  1885.         // *** <wsdl:types> ***
  1886.  
  1887.         foreach ($object->__typedef as $typeName => $typeValue) {
  1888.             // Get/create namespace definition
  1889.  
  1890.             list($nsPrefix, $typeName) = $this->_getTypeNs($typeName);
  1891.  
  1892.             // Create type definition
  1893.  
  1894.             $this->wsdl->complexTypes[$schPrefix][$typeName] = array('name' => $typeName);
  1895.             $thisType =& $this->wsdl->complexTypes[$schPrefix][$typeName];
  1896.  
  1897.             // According to Dmitri's documentation, __typedef comes in two
  1898.             // flavors:
  1899.             // Array = array(array("item" => "value"))
  1900.             // Struct = array("item1" => "value1", "item2" => "value2", ...)
  1901.  
  1902.             if (is_array($typeValue)) {
  1903.                 reset($typeValue);
  1904.                 if (is_array(current($typeValue)) && count($typeValue) == 1
  1905.                     && count(current($typeValue)) == 1) {
  1906.                     // It's an array
  1907.  
  1908.                     $thisType['type'] = 'Array';
  1909.                     reset(current($typeValue));
  1910.                     list($nsPrefix, $typeName) = $this->_getTypeNs(current(current($typeValue)));
  1911.                     $thisType['namespace'] = $nsPrefix;
  1912.                     $thisType['arrayType'] = $typeName . '[]';
  1913.                 } elseif (!is_array(current($typeValue))) {
  1914.                     // It's a struct
  1915.  
  1916.                     $thisType['type'] = 'Struct';
  1917.                     $thisType['order'] = 'all';
  1918.                     $thisType['namespace'] = $nsPrefix;
  1919.                     $thisType['elements'] = array();
  1920.  
  1921.                     foreach ($typeValue as $elementName => $elementType) {
  1922.                         list($nsPrefix, $typeName) = $this->_getTypeNs($elementType);
  1923.                         $thisType['elements'][$elementName]['name'] = $elementName;
  1924.                         $thisType['elements'][$elementName]['type'] = $typeName;
  1925.                         $thisType['elements'][$elementName]['namespace'] = $nsPrefix;
  1926.                     }
  1927.                 } else {
  1928.                     // It's erroneous
  1929.                     return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object));
  1930.                 }
  1931.             } else {
  1932.                 // It's erroneous
  1933.                 return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object));
  1934.             }
  1935.         }
  1936.  
  1937.         // Create an empty element array with the target namespace
  1938.         // prefix, to match the results of WSDL parsing.
  1939.  
  1940.         $this->wsdl->elements[$schPrefix] = array();
  1941.  
  1942.         // Populate tree with message information
  1943.         // *** <wsdl:message> ***
  1944.  
  1945.         foreach ($object->__dispatch_map as $operationName => $messages) {
  1946.             foreach ($messages as $messageType => $messageParts) {
  1947.                 unset($thisMessage);
  1948.  
  1949.                 switch ($messageType) {
  1950.                 case 'in':
  1951.                     $this->wsdl->messages[$operationName . 'Request'] = array();
  1952.                     $thisMessage =& $this->wsdl->messages[$operationName . 'Request'];
  1953.                     break;
  1954.  
  1955.                 case 'out':
  1956.                     $this->wsdl->messages[$operationName . 'Response'] = array();
  1957.                     $thisMessage =& $this->wsdl->messages[$operationName . 'Response'];
  1958.                     break;
  1959.  
  1960.                 case 'alias':
  1961.                     // Do nothing
  1962.                     break;
  1963.  
  1964.                 default:
  1965.                     // Error condition
  1966.                     break;
  1967.                 }
  1968.  
  1969.                 if (isset($thisMessage)) {
  1970.                     foreach ($messageParts as $partName => $partType) {
  1971.                         list ($nsPrefix, $typeName) = $this->_getTypeNs($partType);
  1972.  
  1973.                         $thisMessage[$partName] = array(
  1974.                             'name' => $partName,
  1975.                             'type' => $typeName,
  1976.                             'namespace' => $nsPrefix
  1977.                             );
  1978.                     }
  1979.                 }
  1980.             }
  1981.         }
  1982.  
  1983.         // Populate tree with portType information
  1984.         // XXX Current implementation only supports one portType that
  1985.         // encompasses all of the operations available.
  1986.         // *** <wsdl:portType> ***
  1987.  
  1988.         if (!isset($this->wsdl->portTypes[$service_name . 'Port'])) {
  1989.             $this->wsdl->portTypes[$service_name . 'Port'] = array();
  1990.         }
  1991.         $thisPortType =& $this->wsdl->portTypes[$service_name . 'Port'];
  1992.  
  1993.         foreach ($object->__dispatch_map as $operationName => $messages) {
  1994.             $thisPortType[$operationName] = array('name' => $operationName);
  1995.  
  1996.             foreach ($messages as $messageType => $messageParts) {
  1997.                 switch ($messageType) {
  1998.                 case 'in':
  1999.                     $thisPortType[$operationName]['input'] = array(
  2000.                         'message' => $operationName . 'Request',
  2001.                         'namespace' => $this->tnsPrefix);
  2002.                     break;
  2003.  
  2004.                 case 'out':
  2005.                     $thisPortType[$operationName]['output'] = array(
  2006.                         'message' => $operationName . 'Response',
  2007.                         'namespace' => $this->tnsPrefix);
  2008.                     break;
  2009.                 }
  2010.             }
  2011.         }
  2012.  
  2013.         return true;
  2014.     }
  2015.  
  2016.     /**
  2017.      * Take all the abstract WSDL data and build concrete bindings and
  2018.      * services (destructive).
  2019.      *
  2020.      * XXX Current implementation discards $service_desc.
  2021.      *
  2022.      * @param  $schemaNamespace Namespace for types etc.
  2023.      * @param  $service_name Name of the WSDL <service>
  2024.      * @param  $service_desc Optional description of the WSDL <service>
  2025.      * @access private
  2026.      */
  2027.     function _generateBindingsAndServices($schemaNamespace, $service_name, $service_desc = '')
  2028.     {
  2029.         // Populate tree with bindings information
  2030.         // XXX Current implementation only supports one binding that
  2031.         // matches the single portType and all of its operations.
  2032.         // XXX Is this the correct use of $schemaNamespace here?
  2033.         // *** <wsdl:binding> ***
  2034.  
  2035.         $this->wsdl->bindings[$service_name . 'Binding'] = array(
  2036.                 'type' => $service_name . 'Port',
  2037.                 'namespace' => $this->tnsPrefix,
  2038.                 'style' => 'rpc',
  2039.                 'transport' => SCHEMA_SOAP_HTTP,
  2040.                 'operations' => array());
  2041.         $thisBinding =& $this->wsdl->bindings[$service_name . 'Binding'];
  2042.  
  2043.         foreach ($this->wsdl->portTypes[$service_name . 'Port'] as $operationName => $operationData) {
  2044.             $thisBinding['operations'][$operationName] = array(
  2045.                 'soapAction' => $schemaNamespace . '#' . $operationName,
  2046.                 'style' => $thisBinding['style']);
  2047.  
  2048.             foreach (array('input', 'output') as $messageType)
  2049.                 if (isset($operationData[$messageType])) {
  2050.                     $thisBinding['operations'][$operationName][$messageType] = array(
  2051.                             'use' => 'encoded',
  2052.                             'namespace' => $schemaNamespace,
  2053.                             'encodingStyle' => SOAP_SCHEMA_ENCODING);
  2054.                 }
  2055.         }
  2056.  
  2057.         // Populate tree with service information
  2058.         // XXX Current implementation supports one service which groups
  2059.         // all of the ports together, one port per binding
  2060.         // XXX What about https?
  2061.         // *** <wsdl:service> ***
  2062.  
  2063.         $this->wsdl->services[$service_name . 'Service'] = array('ports' => array());
  2064.         $thisService =& $this->wsdl->services[$service_name . 'Service']['ports'];
  2065.  
  2066.         foreach ($this->wsdl->bindings as $bindingName => $bindingData) {
  2067.             $thisService[$bindingData['type']] = array(
  2068.                     'name' => $bindingData['type'],
  2069.                     'binding' => $bindingName,
  2070.                     'namespace' => $this->tnsPrefix,
  2071.                     'address' => array('location' =>
  2072.                         'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] .
  2073.                         (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')),
  2074.                     'type' => 'soap');
  2075.         }
  2076.  
  2077.         // Set service
  2078.         $this->wsdl->set_service($service_name . 'Service');
  2079.         $this->wsdl->uri = $this->wsdl->namespaces[$this->tnsPrefix];
  2080.  
  2081.         // Create WSDL definition
  2082.         // *** <wsdl:definitions> ***
  2083.  
  2084.         $this->wsdl->definition = array(
  2085.                 'name' => $service_name,
  2086.                 'targetNamespace' => $this->wsdl->namespaces[$this->tnsPrefix],
  2087.                 'xmlns' => SCHEMA_WSDL);
  2088.  
  2089.         foreach ($this->wsdl->namespaces as $nsPrefix => $namespace) {
  2090.             $this->wsdl->definition['xmlns:' . $nsPrefix] = $namespace;
  2091.         }
  2092.     }
  2093.  
  2094.     /**
  2095.      * This function is adapted from Dmitri V's implementation of
  2096.      * DISCO/WSDL generation. It separates namespace from type name in
  2097.      * a __typedef key and creates a new namespace entry in the WSDL
  2098.      * structure if the namespace has not been used before. The
  2099.      * namespace prefix and type name are returned. If no namespace is
  2100.      * specified, xsd is assumed.
  2101.      *
  2102.      * We will not need this function anymore once __typedef is
  2103.      * eliminated.
  2104.      */
  2105.     function _getTypeNs($type)
  2106.     {
  2107.         preg_match_all("'\{(.*)\}'sm", $type, $m);
  2108.         if (isset($m[1][0]) && $m[1][0] != '') {
  2109.             if (!array_key_exists($m[1][0], $this->wsdl->ns)) {
  2110.                 $ns_pref = 'ns' . count($this->wsdl->namespaces);
  2111.                 $this->wsdl->ns[$m[1][0]] = $ns_pref;
  2112.                 $this->wsdl->namespaces[$ns_pref] = $m[1][0];
  2113.             }
  2114.             $typens = $this->wsdl->ns[$m[1][0]];
  2115.             $type = ereg_replace($m[0][0], '', $type);
  2116.         } else {
  2117.             $typens = 'xsd';
  2118.         }
  2119.  
  2120.         return array($typens, $type);
  2121.     }
  2122.  
  2123. }
  2124.